package com.dl.config.shiro;

import com.dl.utils.ApplicationContextUtils;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.ExcessiveAttemptsException;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.support.atomic.RedisAtomicLong;

import java.util.concurrent.TimeUnit;

/**
 * 自定义密码匹配器，限制密码尝试次数
 * @author DL
 * @create 2021-06-09 16:43
 */
public class RetryLimitCredentialsMatcher extends HashedCredentialsMatcher {

    private long RetryLimitNumber;//尝试次数，从0开始，共5次

    public void setRetryLimitNumber(long retryLimitNumber) {
        RetryLimitNumber = retryLimitNumber;
    }

    @SuppressWarnings("unchecked")
    private RedisTemplate<String, Object> getRedisTemplate(){
        //获取redis操作对象
        return (RedisTemplate<String, Object>) ApplicationContextUtils.getBean("redisTemplate");
    }

    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        String username = (String) token.getPrincipal();
        // 1.获取系统中是否有登录次数的缓存，缓存对象构建为"用户名-登录次数"
        assert getRedisTemplate().getConnectionFactory() != null;
        RedisAtomicLong atomicLong = new RedisAtomicLong(username, getRedisTemplate().getConnectionFactory());

        // 2.如果之前没有登录缓存，则创建一个登录次数缓存
        long cacheNumber = atomicLong.get();

        // 3.如果缓存次数已经超过限制，则驳回此次请求
        if (cacheNumber > RetryLimitNumber){
            atomicLong.expire(10, TimeUnit.MINUTES);
            throw new ExcessiveAttemptsException("密码输入错误5次，请10分钟后重试！");
        }

        // 4.将缓存记录次数加1，并设置超时时间
        atomicLong.incrementAndGet();
        //atomicLong.expire(10, TimeUnit.MINUTES);

        // 5.验证用户本次输入的账号密码，如果登录成功，则清除登录次数的缓存
        boolean flag = super.doCredentialsMatch(token, info);
        if (flag){
            atomicLong.expire(0,TimeUnit.MINUTES);
        }
        return flag;
    }
}
